package com.asiainfo.simpledao;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.sql.DataSource;

public abstract class AbstractDao implements Dao {

	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();

	private DataSource dataSource;

	public AbstractDao(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public AbstractDao(String url, String user, String password) {
		this.dataSource = new DefaultDataSource(url, user, password);
	}

	public Connection getConnection() {
		try {
			Connection connection = connectionHolder.get();
			if (connection == null) {
				connection = dataSource.getConnection();
				connection.setAutoCommit(false);
				connectionHolder.set(connection);
			}
			return connection;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public void closeConnection() {
		JdbcUtil.close(getConnection());
		connectionHolder.remove();
	}

	@Override
	public int execSQL(String sql, Object... params) {
		try {
			return JdbcUtil.execSQL(getConnection(), sql, params);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public int execSQL(String sql, Map<String, Object> params) {
		try {
			return JdbcUtil.execSQL(getConnection(), sql, params);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Row selectOne(String sql, Object... params) {
		ResultSet rs = null;
		try {
			rs = JdbcUtil.queryForResultSet(getConnection(), sql, params);
			return JdbcUtil.getRow(rs);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(rs);
		}
	}

	@Override
	public Row selectOne(String sql, Map<String, Object> params) {
		ResultSet rs = null;
		try {
			rs = JdbcUtil.queryForResultSet(getConnection(), sql, params);
			return JdbcUtil.getRow(rs);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(rs);
		}
	}

	@Override
	public List<Row> selectList(String sql, Object... params) {
		ResultSet rs = null;
		try {
			rs = JdbcUtil.queryForResultSet(getConnection(), sql, params);
			return JdbcUtil.getRows(rs);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(rs);
		}
	}

	@Override
	public List<Row> selectList(String sql, Map<String, Object> params) {
		ResultSet rs = null;
		try {
			rs = JdbcUtil.queryForResultSet(getConnection(), sql, params);
			return JdbcUtil.getRows(rs);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(rs);
		}
	}

	@Override
	public abstract Page selectList(String sql, int currPage, int pageSize, Map<String, Object> params);

	@Override
	public abstract Page selectList(String sql, int currPage, int pageSize, Object... params);

	@Override
	public void commit() {
		JdbcUtil.commit(getConnection());
	}

	@Override
	public void rollBack() {
		JdbcUtil.rollback(getConnection());
	}

	protected int count(String sql, Map<String, Object> params) {
		ResultSet rs = null;
		try {
			String countSql = getCountSql(sql);
			rs = JdbcUtil.queryForResultSet(getConnection(), countSql, params);
			rs.next();
			return rs.getInt(1);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(rs);
		}
	}

	protected int count(String sql, Object... params) {
		ResultSet rs = null;
		try {
			String countSql = getCountSql(sql);
			rs = JdbcUtil.queryForResultSet(getConnection(), countSql, params);
			rs.next();
			return rs.getInt(1);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			JdbcUtil.close(rs);
		}
	}

	private String getCountSql(String sql) {
		String sqlUse = sql;
		int order_by = sql.toUpperCase().lastIndexOf("ORDER BY");
		if (order_by > -1) {
			sqlUse = sql.substring(0, order_by);
		}
		StringBuffer countSql = new StringBuffer();
		countSql.append("SELECT COUNT(*) FROM (").append(sqlUse).append(") A");
		return countSql.toString();
	}

	private static class DefaultDataSource implements DataSource {
		private final String url;
		private final String user;
		private final String password;

		public DefaultDataSource(String url, String user, String password) {
			this.url = url;
			this.user = user;
			this.password = password;
		}

		@Override
		public PrintWriter getLogWriter() throws SQLException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void setLogWriter(PrintWriter out) throws SQLException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void setLoginTimeout(int seconds) throws SQLException {
			throw new UnsupportedOperationException();
		}

		@Override
		public int getLoginTimeout() throws SQLException {
			throw new UnsupportedOperationException();
		}

		@Override
		public Logger getParentLogger() throws SQLFeatureNotSupportedException {
			throw new UnsupportedOperationException();
		}

		@Override
		public <T> T unwrap(Class<T> iface) throws SQLException {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean isWrapperFor(Class<?> iface) throws SQLException {
			throw new UnsupportedOperationException();
		}

		@Override
		public Connection getConnection() throws SQLException {
			return DriverManager.getConnection(url, user, password);
		}

		@Override
		public Connection getConnection(String username, String password) throws SQLException {
			throw new UnsupportedOperationException();
		}

	}

}
